using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Indiefreaks.Xna.Core;
using Indiefreaks.Xna.Logic;
using Indiefreaks.Xna.Threading;
using Microsoft.Xna.Framework;
using SynapseGaming.LightingSystem.Rendering;

namespace Indiefreaks.Xna.Network
{
    /// <summary>
    /// The Session class is the abstract access to a game session created with a SessionManager instance
    /// </summary>
    public abstract class Session : IUpdate
    {
        protected readonly Dictionary<ushort, Command> Commands = new Dictionary<ushort, Command>();
        protected readonly Queue<Command> CommandsToSynchronize = new Queue<Command>();
        protected readonly Dictionary<int, ISceneEntity> RegisteredEntities = new Dictionary<int, ISceneEntity>();
        protected readonly Queue<ISceneEntity> SceneEntitiesToSynchronize = new Queue<ISceneEntity>();

        private readonly List<NonPlayerAgent> _nonPlayerAgents = new List<NonPlayerAgent>();
        private readonly List<PlayerAgent> _playerAgents = new List<PlayerAgent>();

        /// <summary>
        /// Returns the list of players in the current session
        /// </summary>
        public abstract IList<IdentifiedPlayer> AllPlayers { get; }

        /// <summary>
        /// Return the list of local players in the current session
        /// </summary>
        public abstract IList<IdentifiedPlayer> LocalPlayers { get; }

        /// <summary>
        /// Returns the list of remote players in the current session
        /// </summary>
        public abstract IList<IdentifiedPlayer> RemotePlayers { get; }

        /// <summary>
        /// Returns the current SessionState
        /// </summary>
        public abstract SessionState Status { get; }

        /// <summary>
        /// Returns the current Session SessionType
        /// </summary>
        public abstract SessionType SessionType { get; }

        /// <summary>
        /// Returns if the current Session authorizes Host migration
        /// </summary>
        public abstract bool AllowHostMigration { get; }

        /// <summary>
        /// Returns if the current Session authorizes players to join the Session while playing
        /// </summary>
        public abstract bool AllowJoinInProgress { get; }

        /// <summary>
        /// Returns the current Session Host player
        /// </summary>
        public abstract IdentifiedPlayer Host { get; }

        /// <summary>
        /// Returns the current Session Bytes/Second received
        /// </summary>
        public abstract int BytesPerSecondReceived { get; }

        /// <summary>
        /// Returns the current Session Bytes/Second sent
        /// </summary>
        public abstract int BytesPerSecondSent { get; }

        /// <summary>
        /// Returns if the current Session is Host
        /// </summary>
        public abstract bool IsHost { get; }

        #region IUpdate Members

        /// <summary>
        /// Update loop call
        /// </summary>
        /// <param name="gameTime"/>
        void IUpdate.Update(GameTime gameTime)
        {
            if (IsHost)
            {
                if (CommandsToSynchronize.Count != 0)
                {
                    for (int i = 0; i < CommandsToSynchronize.Count; i++)
                    {
                        Command command = CommandsToSynchronize.Dequeue();
                        command.Id = command.LocalId;
                        SynchronizeCommandOnClients(command);
                        CommandsToSynchronize.Enqueue(command);
                    }
                }
                if (SceneEntitiesToSynchronize.Count != 0)
                {
                    for (int i = 0; i < SceneEntitiesToSynchronize.Count; i++)
                    {
                        ISceneEntity entity = SceneEntitiesToSynchronize.Dequeue();
                        SynchronizeSceneEntitiesOnClients(entity);
                        SceneEntitiesToSynchronize.Enqueue(entity);
                    }
                }
            }

            // we loop through all local PlayerAgents to retrieve input commands
            foreach (PlayerAgent playerAgent in _playerAgents)
            {
                playerAgent.Process(gameTime);
            }

            foreach (NonPlayerAgent nonPlayerAgent in _nonPlayerAgents)
            {
                nonPlayerAgent.Process(gameTime);
            }


            // we update the session (sending & retrieving network data)
            Update(gameTime);

            // we retrieve all data incoming from the network
            ListenIncoming();
        }

        #endregion

        /// <summary>
        /// Raised when a player left the Session
        /// </summary>
        public event EventHandler<IdentifiedPlayerEventArgs> PlayerLeft;

        /// <summary>
        /// Raised when a player joined the Session
        /// </summary>
        public event EventHandler<IdentifiedPlayerEventArgs> PlayerJoined;

        /// <summary>
        /// Raised when a player left the Session
        /// </summary>
        /// <param name="identifiedPlayer"></param>
        protected void OnPlayerLeft(IdentifiedPlayer identifiedPlayer)
        {
            if (PlayerLeft != null)
                PlayerLeft(this, new IdentifiedPlayerEventArgs(identifiedPlayer));
        }

        /// <summary>
        /// Raised when a player joined the Session
        /// </summary>
        /// <param name="identifiedPlayer"></param>
        protected void OnPlayerJoined(IdentifiedPlayer identifiedPlayer)
        {
            if (PlayerJoined != null)
                PlayerJoined(this, new IdentifiedPlayerEventArgs(identifiedPlayer));
        }

        /// <summary>
        /// Asks the Session to send a remote command call on the session host
        /// </summary>
        /// <param name="command">The command that should be executed</param>
        public abstract void ExecuteCommandOnServer(Command command);

        /// <summary>
        /// Asks the Session to send a remote command call on all session clients
        /// </summary>
        /// <param name="command">The command that should be executed</param>
        public abstract void ExecuteServerCommandOnClients(Command command);

        /// <summary>
        /// Update loop call
        /// </summary>
        /// <param name="gameTime"/>
        public abstract void Update(GameTime gameTime);

        /// <summary>
        /// Starts the Session and changes its SessionState from Lobby to Playing
        /// </summary>
        public abstract void StartSession();

        /// <summary>
        /// Raised when the session started
        /// </summary>
        public event EventHandler SessionStarted;

        /// <summary>
        /// Raised when the session started
        /// </summary>
        protected void OnSessionStarted()
        {
            if (SessionStarted != null)
                SessionStarted(this, EventArgs.Empty);
        }

        /// <summary>
        /// Ends the Session and changes its SessionState from Playing to Ended
        /// </summary>
        public abstract void EndSession();

        /// <summary>
        /// Raised when the Session ended
        /// </summary>
        public event EventHandler SessionEnded;

        /// <summary>
        /// Raised when the Session ended
        /// </summary>
        protected void OnSessionEnded()
        {
            if (SessionEnded != null)
                SessionEnded(this, EventArgs.Empty);
        }

        /// <summary>
        /// Creates a new PlayerAgent
        /// </summary>
        /// <param name="identifiedPlayer">The identified player instance</param>
        public PlayerAgent CreatePlayerAgent(IdentifiedPlayer identifiedPlayer)
        {
            return CreatePlayerAgent<PlayerAgent>(identifiedPlayer);
        }

        /// <summary>
        /// Creates a new PlayerAgent
        /// </summary>
        /// <param name="identifiedPlayer">The identified player instance</param>
        public T CreatePlayerAgent<T>(IdentifiedPlayer identifiedPlayer) where T : PlayerAgent, new()
        {
            if (Status != SessionState.Playing)
                throw new CoreException("Session must be started to create a PlayerAgent");

            var playerAgent = new T {IdentifiedPlayer = identifiedPlayer};
            _playerAgents.Add(playerAgent);

            return playerAgent;
        }

        /// <summary>
        /// Creates a new NonPlayerAgent
        /// </summary>
        /// <returns>Returns a new NonPlayerAgent instance</returns>
        public NonPlayerAgent CreateNonPlayerAgent()
        {
            return CreateNonPlayerAgent<NonPlayerAgent>();
        }

        public T CreateNonPlayerAgent<T>() where T : NonPlayerAgent, new()
        {
            if (Status != SessionState.Playing)
                throw new CoreException("Session must be started to create a PlayerAgent");

            var nonPlayerAgent = new T();
            _nonPlayerAgents.Add(nonPlayerAgent);

            return nonPlayerAgent;
        }

        /// <summary>
        /// Registers a command on the Session host and remotely creates the command on every client
        /// </summary>
        /// <param name="command">The command to be registered</param>
        internal void RegisterCommand(Command command)
        {
            CommandsToSynchronize.Enqueue(command);
        }

        /// <summary>
        /// Registers a SceneEntity on the session host and remotely synchronize its unique Id on every client
        /// </summary>
        /// <param name="sceneEntity"></param>
        internal void RegisterSceneEntity(ISceneEntity sceneEntity)
        {
            if(!RegisteredEntities.ContainsKey(sceneEntity.UniqueId))
                SceneEntitiesToSynchronize.Enqueue(sceneEntity);
        }
        
        public T GetRegisteredEntity<T>(int uniqueId) where T : class
        {
            if (RegisteredEntities.ContainsKey(uniqueId))
                return RegisteredEntities[uniqueId] as T;

            return null;
        }

        internal abstract void SynchronizeCommandOnClients(Command command);

        internal abstract void SynchronizeSceneEntitiesOnClients(ISceneEntity sceneEntity);

        /// <summary>
        /// Listens to all data received from the network interface
        /// </summary>
        protected abstract void ListenIncoming();
    }
}